using System;
using System.Threading;
using System.Net;
using System.Net.Sockets;

namespace SmartBoard.Core.Networking
{
    /// <summary>
    /// Client socket creator.
    /// </summary>
    public class SocketConnector : BaseSocketConnectionCreator
    {
        #region Private Fields
        private Timer _reconnectTimer;
        private int _reconnectAttempted;
        #endregion

        #region Constructor
        public SocketConnector(BaseSocketConnectionHost host, string name, IPEndPoint remoteEndPoint, ProxyInfo proxyData, EncryptionType encryptionType, CompressionType compressionType, ICryptoService cryptoService, int reconnectAttempts, int reconnectAttemptInterval, IPEndPoint localEndPoint)
            : base(host, name, localEndPoint, encryptionType, compressionType, cryptoService)
        {

            _reconnectTimer = new Timer(ReconnectConnectionTimerCallBack);
            _reconnectAttempted = 0;

            RemoteEndPoint = remoteEndPoint;
            ReconnectAttempts = reconnectAttempts;
            ReconnectAttemptInterval = reconnectAttemptInterval;
            ProxyInfo = proxyData;
        }
        #endregion

        #region Properties

        public int ReconnectAttempts 
        { 
            get; set; 
        }

        public int ReconnectAttemptInterval
        {
            get; set;
        }

        public IPEndPoint RemoteEndPoint
        {
            get; private set;
        }
        
        public ProxyInfo ProxyInfo
        {
            get; private set;
        }

        public IPEndPoint LocalEndPoint
        {
            get { return InternalLocalEndPoint; }
            set { InternalLocalEndPoint = value; }
        }

        internal Socket Socket
        {
            get; private set;
        }

        #endregion

        #region Public Methods

        public override void Start()
        {
            this.CheckDisposedWithException();

            BeginConnect();
        }

        public override void Stop()
        {
            Dispose();
        }
        #endregion


        internal void ReconnectConnection(bool resetAttempts, Exception ex)
        {
            if (IsDisposed) return;

            if (resetAttempts)
            {
                //----- Reset counter and start new connect!
                _reconnectAttempted = 0;
                _reconnectTimer.Change(ReconnectAttemptInterval, ReconnectAttemptInterval);
            }
            else
            {
                //----- Check attempt count!
                if (ReconnectAttempts > 0)
                {
                    if (_reconnectAttempted < ReconnectAttempts)
                    {
                        Host.FireOnException(null, new ReconnectAttemptException("Reconnect attempt", this, ex, _reconnectAttempted, false));
                        _reconnectTimer.Change(ReconnectAttemptInterval, ReconnectAttemptInterval);
                    }
                    else
                    {
                        Host.FireOnException(null, new ReconnectAttemptException("Reconnect attempt", this, ex, _reconnectAttempted, true));
                    }
                }
                else
                {
                    Host.FireOnException(null, new ReconnectAttemptException("Reconnect attempt", this, ex, _reconnectAttempted, true));
                }
            }
        }

        #region Private Methods

        private void BeginConnect()
        {
            if (IsDisposed) return;

            //----- Create Socket!
            Socket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
            Socket.Bind(InternalLocalEndPoint);
            Socket.ReceiveBufferSize = Host.SocketBufferSize;
            Socket.SendBufferSize = Host.SocketBufferSize;

            _reconnectTimer.Change(Timeout.Infinite, Timeout.Infinite);

            var e = new SocketAsyncEventArgs
            {
                UserToken = this
            };
            e.Completed += BeginConnectCallbackAsync;

            if (ProxyInfo == null)
            {
                e.RemoteEndPoint = RemoteEndPoint;
            }
            else
            {
                ProxyInfo.Completed = false;
                ProxyInfo.SOCKS5Phase = SOCKS5Phase.Idle;
                e.RemoteEndPoint = ProxyInfo.ProxyEndPoint;
            }

            if (!Socket.ConnectAsync(e))
            {
                BeginConnectCallbackAsync(this, e);
            }
        }

        private void BeginConnectCallbackAsync(object sender, SocketAsyncEventArgs e)
        {
            if (!IsDisposed)
            {
                BaseSocketConnection connection = null;
                SocketConnector connector = null;
                Exception exception = null;

                if (e.SocketError == SocketError.Success)
                {
                    try
                    {
                        connector = (SocketConnector)e.UserToken;
                        connection = new ClientSocketConnection(Host, connector, connector.Socket);

                        //----- Adjust buffer size!
                        connector.Socket.ReceiveBufferSize = Host.SocketBufferSize;
                        connector.Socket.SendBufferSize = Host.SocketBufferSize;

                        //----- Initialize!
                        Host.AddSocketConnection(connection);
                        connection.Active = true;
                        Host.InitializeConnection(connection);
                    }
                    catch (Exception ex)
                    {
                        exception = ex;
                        if (connection != null)
                        {
                            Host.DisposeConnection(connection);
                            Host.RemoveSocketConnection(connection);
                            connection = null;
                        }
                    }
                }
                else
                {
                    exception = new SocketException((int)e.SocketError);
                }

                if (exception != null)
                {
                    _reconnectAttempted++;
                    ReconnectConnection(false, exception);
                }
            }

            e.UserToken = null;
            e.Dispose();
            e = null;
        }

        private void ReconnectConnectionTimerCallBack(Object stateInfo)
        {
            if (IsDisposed) return;

            _reconnectTimer.Change(Timeout.Infinite, Timeout.Infinite);
            BeginConnect();
        }
        #endregion

        #region Destructor

        protected override void Free(bool canAccessFinalizable)
        {
            if (_reconnectTimer != null)
            {
                _reconnectTimer.Dispose();
                _reconnectTimer = null;
            }

            if (Socket != null)
            {
                Socket.Close();
                Socket = null;
            }

            RemoteEndPoint = null;
            ProxyInfo = null;

            base.Free(canAccessFinalizable);
        }

        #endregion
    }
}